package ufrgs.ia.tetralath.models;

import java.awt.Point;
import java.util.ArrayList;


import ufrgs.ia.Utils.Tools;
import ufrgs.ia.tetralath.Tetralath;
import ufrgs.ia.tetralath.Tetralath.BOARD_STATE;
import ufrgs.ia.tetralath.Tetralath.PLAYER;

/**
 * Represents a current state of the game and possibly its new moves
 * @author Fernando
 *
 */
public class GameMomentum implements Cloneable {

	/** heuristic value for this momentum. CURRENTLY: 10 - WIN; -10 - LOSS; 0 - DRAW; 2 - NONE */
	private int newBoardValue;
	
	/** gameboard that represents the current game momentum, with the new move already placed */
	private int[][] gameBoard;
	
	/** piece that represents the distinction from the received gameboard and the new move */
	private Point newMove;
	
	/** true if this momentum is a DRAW, WIN or LOSS condition and false if its a NONE */
	private boolean isLeaf;
	
	/** owner of the piece located in newMove */
	private PLAYER newMoveOwner;
	
	/** List of successors for the current momentum */
	private ArrayList<GameMomentum> sucessorsList = new ArrayList<GameMomentum>();
	
	/** This Game State according to the value of this board */
	private BOARD_STATE momentumState = BOARD_STATE.NONE;
	
	public GameMomentum(int[][] _gameBoard, Point _newMove, PLAYER _owner) {
		gameBoard = _gameBoard;
		newMove = _newMove;
		newMoveOwner = _owner;
		
		if(_newMove != null)
			_gameBoard[_newMove.x][_newMove.y] = _owner.getValue();
		
		setValue();
	}
	
	/**
	 * This constructor is going to me used only for the First Momentum
	 * @param _gameBoard - a new gameboard with no owner for any piece
	 */
	public GameMomentum(int[][] _gameBoard) {
		gameBoard = _gameBoard;
	}
	
	/**
	 * Adds a new sucessor to the current GameMomentum
	 * @param mementum
	 */
	public void addNewSuccessor(GameMomentum mementum) {
		if(sucessorsList != null) {
			sucessorsList.add(mementum);
		}
	}
	
	public void removeState(GameMomentum state) {
		if(sucessorsList != null) {
			sucessorsList.remove(state);
		}
	}
	
	public int getValue() {
		return newBoardValue;
	}
	
	public boolean isLeaf() {
		return isLeaf;
	}
	
	@SuppressWarnings("unchecked")
	public ArrayList<GameMomentum> getSucessors() {
		return (ArrayList<GameMomentum>)sucessorsList.clone(); 
	}
	
	public Point getNewMove() {
		return newMove;
	}
	
	public int[][] getGameBoard() {
		return Tools.getMatrixClone(gameBoard);
	}
	
	public void setNewMove(Point _newMove, PLAYER _player) {
		newMove = _newMove;
		gameBoard[_newMove.x][_newMove.y] = _player.getValue();
	}
	
	/**
	 * Evaluates the current state of the game for the given player
	 * @param currentState - current game state, must contain the next move
	 * @param player - player turn
	 * @return the value of the current state (very good if win, very bad if loss, neutral if draw and other values in other cases)
	 */
	private void setValue() {

		momentumState = getBoardState(newMoveOwner);
		
		isLeaf = true;
		
		if(momentumState == BOARD_STATE.NONE)
			isLeaf = false;
		
		newBoardValue = momentumState.getValue();
		
		// TODO: we must create other criteria to set values to other states
	}
	
	public void generateSucessors(PLAYER _player) {
		
		int[][] newBoard;
		sucessorsList.clear();
			
		for(int rowIndex = 0; rowIndex < Tetralath.BOARD_DIMENSION; rowIndex++) {
			for(int columnIndex = 0; columnIndex < Tetralath.BOARD_DIMENSION; columnIndex++) {
				
				if((Tetralath.isPositionValid(rowIndex, columnIndex)) && (gameBoard[rowIndex][columnIndex] == 0)) {
					newBoard = getGameBoard();
					newBoard[rowIndex][columnIndex] = _player.getValue();
					addNewSuccessor(new GameMomentum(newBoard, new Point(rowIndex, columnIndex), _player));
				}
			}
		}	
	}
	
	/**
	 * For a given game state checks whether this is a state of loss, victory, draw or none
	 * @param _gameState - current game momentum
	 * @param _currentPlayer - current player
	 * @return a game state of victory, loss, draw or none for the given player
	 */
	public BOARD_STATE getBoardState(PLAYER _currentPlayer) {

		BOARD_STATE currentBoardState;
		ArrayList<Point> horizontalPieces;
		ArrayList<Point> diagonalPieces;
		ArrayList<Point> reverseDiagonalPieces;

		int x = getNewMove().x;
		int y = getNewMove().y;

		horizontalPieces = getHoleChain(x, y, 0, 1, gameBoard, _currentPlayer);
		horizontalPieces.addAll(getHoleChain(x, y, 0, -1, gameBoard, _currentPlayer));

		diagonalPieces = getHoleChain(x, y, 1, 0, gameBoard, _currentPlayer);
		diagonalPieces.addAll(getHoleChain(x, y, -1, 0, gameBoard, _currentPlayer));

		reverseDiagonalPieces = getHoleChain(x, y, 1, 1, gameBoard, _currentPlayer);
		reverseDiagonalPieces.addAll(getHoleChain(x, y, -1, -1, gameBoard, _currentPlayer));
		
		
		
		currentBoardState = checkWinLosDrawState(horizontalPieces, diagonalPieces, reverseDiagonalPieces, _currentPlayer);
		
		
		if(currentBoardState == BOARD_STATE.NONE) {
			Point blockPoint = getBlockPoint(x, y, 0, 1, gameBoard, _currentPlayer);
			
			if(blockPoint == null) {
				blockPoint = getBlockPoint(x, y, 1, 0, gameBoard, _currentPlayer);
				if(blockPoint == null)
					blockPoint = getBlockPoint(x, y, 1, 1, gameBoard, _currentPlayer);
			}
			
			if(blockPoint != null) {
				if(_currentPlayer == PLAYER.MINIMAX) {
					currentBoardState = BOARD_STATE.MINIMAX_MOVE_BLOCK;
				} else {
					currentBoardState = BOARD_STATE.HUMAN_MOVE_BLOCK;
				}
			}
		}
		
		
		
		
		return currentBoardState;
	}
	
	private BOARD_STATE checkWinLosDrawState(ArrayList<Point> horizontalPieces, ArrayList<Point> diagonalPieces,
			ArrayList<Point> reverseDiagonalPieces, PLAYER currentPlayer) {
		
		BOARD_STATE currentBoardState = BOARD_STATE.NONE;
		
		if(currentPlayer == PLAYER.MINIMAX) {
		
			if (horizontalPieces != null) {
				if (horizontalPieces.size() >= 3)
					currentBoardState = BOARD_STATE.MINIMAX_MOVE_WIN;
				else if (horizontalPieces.size() >= 2)
					currentBoardState = BOARD_STATE.MINIMAX_MOVE_LOSS;
			}
	
			if (diagonalPieces != null) {
				if (diagonalPieces.size() >= 3)
					currentBoardState = BOARD_STATE.MINIMAX_MOVE_WIN;
				else if (diagonalPieces.size() >= 2 && currentBoardState != BOARD_STATE.MINIMAX_MOVE_WIN)
					currentBoardState = BOARD_STATE.MINIMAX_MOVE_LOSS;
			}
	
			if (reverseDiagonalPieces != null) {
				if (reverseDiagonalPieces.size() >= 3)
					currentBoardState = BOARD_STATE.MINIMAX_MOVE_WIN;
				else if (reverseDiagonalPieces.size() >= 2 && currentBoardState != BOARD_STATE.MINIMAX_MOVE_WIN)
					currentBoardState = BOARD_STATE.MINIMAX_MOVE_LOSS;
			}
			
		} else {
			if (horizontalPieces != null) {
				if (horizontalPieces.size() >= 3)
					currentBoardState = BOARD_STATE.HUMAN_MOVE_LOSS;
				else if (horizontalPieces.size() >= 2)
					currentBoardState = BOARD_STATE.HUMAN_MOVE_WIN;
			}

			if (diagonalPieces != null) {
				if (diagonalPieces.size() >= 3)
					currentBoardState = BOARD_STATE.HUMAN_MOVE_LOSS;
				else if (diagonalPieces.size() >= 2 && currentBoardState != BOARD_STATE.HUMAN_MOVE_LOSS)
					currentBoardState = BOARD_STATE.HUMAN_MOVE_WIN;
			}

			if (reverseDiagonalPieces != null) {
				if (reverseDiagonalPieces.size() >= 3)
					currentBoardState = BOARD_STATE.HUMAN_MOVE_LOSS;
				else if (reverseDiagonalPieces.size() >= 2 && currentBoardState != BOARD_STATE.HUMAN_MOVE_LOSS)
					currentBoardState = BOARD_STATE.HUMAN_MOVE_WIN;
			}
		}
		
		if(currentBoardState == BOARD_STATE.NONE && isDraw(getGameBoard()))
			currentBoardState = BOARD_STATE.DRAW;
		
		return currentBoardState;
	}
	
	/**
	 * Creates an array with the biggest chain of cells
	 * 
	 * @param originX - x position of clicked cell
	 * @param originY - y position of clicked cell
	 * @param directionX - -1 to decrease line, 0 to don't move, +1 to increase line
	 * @param directionY - -1 to decrease row, 0 to don't move, +1 to increase row
	 * @return - An array with the points that are connected and have the same owner in the specified direction
	 */
	private static ArrayList<Point> getHoleChain(int originX, int originY, int directionX, int directionY, int[][] _gameBoard, PLAYER _currentPlayer) {
		ArrayList<Point> chain = new ArrayList<Point>();
		int step = 1;

		while (Tetralath.isPositionValid(originX + step * directionX, originY + step * directionY)) {
				
			if (_gameBoard[originX + step * directionX][originY + step * directionY] == _currentPlayer.getValue())
				chain.add(new Point(originX + step * directionX, originY + step * directionY));
			else
				break;

			if (chain.size() == 3)
				break;

			step++;
		}

		return chain;
	}
	
	private static Point getBlockPoint(int originX, int originY, int directionX, int directionY, int[][] _gameBoard, PLAYER _currentPlayer) {
		
		ArrayList<Point> chain = new ArrayList<Point>();
		int step = 1;
		boolean pointFound = false;
		
		PLAYER other;
		
		if(_currentPlayer == PLAYER.HUMAN)
			other = PLAYER.MINIMAX;
		else
			other = PLAYER.HUMAN;
		
		while (Tetralath.isPositionValid(originX + step * directionX, originY + step * directionY)) {
				
			if (_gameBoard[originX + step * directionX][originY + step * directionY] == other.getValue())
				chain.add(new Point(originX + step * directionX, originY + step * directionY));
			else
				break;

			if (chain.size() == 3) {
				pointFound = true;
				break;
			}

			step++;
		}
		
		step = 1;
		
		if(!pointFound) {
			while (Tetralath.isPositionValid(originX + step * (-directionX), originY + step * (-directionY))) {
				
				if (_gameBoard[originX + step * (-directionX)][originY + step * (-directionY)] == other.getValue()) {
					chain.add(new Point(originX + step * (-directionX), originY + step * (-directionY)));
				} else {
					break;
				}
	
				if (chain.size() == 3) {
					pointFound = true;
					break;
				}
				step++;
			}
		}

		
		if(pointFound)
			return new Point(originX, originY);
		else
			return null;
	}

	/**
	 * Checks if there are empty slots in the game board
	 * 
	 * @return true if current state is a draw
	 */
	public static boolean isDraw(int[][] _gameBoard) {
		boolean isDraw = true;

		for (int i = 0; i < Tetralath.BOARD_DIMENSION; i++) {
			for (int j = 0; j < Tetralath.BOARD_DIMENSION; j++) {
				if (_gameBoard[i][j] == 0) {
					isDraw = false;
					break;
				}
			}
		}

		return isDraw;
	}
}
